<template>
  <div class="warning-page" :class="{ compact: isCompact }">
    <el-card class="toolbar" shadow="never">
      <el-form :inline="true" :model="form" @submit.prevent>
        <el-form-item label="城市">
          <el-input v-model="form.city" placeholder="例如：上海 / Shanghai" clearable @keyup.enter.native="loadAll" style="width: 220px" />
        </el-form-item>
        <el-form-item label="天数">
          <el-select v-model="form.days" style="width: 120px" @change="() => { loadForecast(); loadForecastText(); }">
            <el-option v-for="d in [3,5,7,10,14]" :key="d" :value="d" :label="d + ' 天'" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" :loading="loading.forecast || loading.wind" @click="loadAll">查询</el-button>
        </el-form-item>
        <el-form-item label="风向统计城市（可选）">
          <el-select v-model="form.cities" multiple clearable filterable placeholder="默认全国样本" style="min-width: 320px" @change="loadWind">
            <el-option v-for="c in defaultCities" :key="c" :label="c" :value="c" />
          </el-select>
        </el-form-item>
      </el-form>
    </el-card>

    <el-card class="panel panel-full forecast-card" shadow="hover">
      <template #header>
        <div class="card-header">
          <span>文字预报</span>
          <span v-if="currentTemp !== null" class="temp-chip">当前 {{ currentTemp }}℃</span>
        </div>
      </template>
      <div v-if="!loading.forecast && forecastText" class="forecast-box">
        <ul v-if="forecastItems.length" class="forecast-list">
          <li v-for="it in forecastItems" :key="it.date" class="forecast-item">
            <span class="date">{{ it.date }}</span>
            <span class="tag tag--high">最高 {{ it.high }}℃</span>
            <span class="tag tag--low">最低 {{ it.low }}℃</span>
          </li>
        </ul>
        <pre v-else class="forecast-raw">{{ forecastText }}</pre>
      </div>
      <div v-else class="forecast-raw" style="color:#909399">暂无文字预报</div>
    </el-card>

    <div class="content">
      <el-card class="panel panel--half" shadow="hover">
        <template #header>
          <div class="card-header">
            <span>未来 {{ form.days }} 天温度趋势（{{ form.city || '—' }}）</span>
          </div>
        </template>
        <div ref="forecastRef" class="chart chart--compact"></div>
        <el-skeleton v-if="loading.forecast" animated :rows="4" />
      </el-card>

      <el-card class="panel panel--half" shadow="hover">
        <template #header>
          <div class="card-header">
            <span>全国风向分布（可按所选城市统计）</span>
          </div>
        </template>
        <div ref="windRef" class="chart"></div>
        <el-skeleton v-if="loading.wind" animated :rows="4" />
      </el-card>

      <el-card class="panel panel--half" shadow="hover">
        <template #header>
          <div class="card-header">
            <span>地图（百度）—— {{ form.city || '请选择城市' }}</span>
          </div>
        </template>
        <div ref="mapRef" class="map"></div>
      </el-card>

      <el-card class="panel panel-full" shadow="hover">
        <template #header>
          <div class="card-header">
            <span>天气预警（未来7天）</span>
          </div>
        </template>
        <div v-if="loading.warning">
          <el-skeleton animated :rows="4" />
        </div>
        <div v-else-if="warningData" class="warning-card">
          <div class="warning-summary">
            <span class="summary-text">{{ warningData.summary || '本周整体风险较低。' }}</span>
            <span class="level-chip" :class="'level-'+warningData.maxLevel">最高等级 {{ warningData.maxLevel }}</span>
          </div>
          <ul class="warning-list">
            <li v-for="d in warningData.details" :key="d.date" class="warning-item">
              <div class="warning-date">{{ d.date }}</div>
              <div class="warning-tags">
                <span class="level-badge" :class="'level-'+d.level">L{{ d.level }}</span>
                <span v-for="(it, idx) in d.items" :key="idx" class="warn-tag">{{ it }}</span>
              </div>
            </li>
          </ul>
        </div>
        <div v-else class="warning-empty">暂无预警数据</div>
      </el-card>
    </div>
  </div>
  
</template>

<script setup lang="ts">
import { onMounted, onBeforeUnmount, onActivated, reactive, ref, watch, computed, nextTick } from 'vue'
import { useRoute, onBeforeRouteUpdate } from 'vue-router'
import * as echarts from 'echarts'
import { ElMessage } from 'element-plus'
import { MES_workbenchApi } from '@/utils/Interceptors'

type ApiResult<T> = { code?: number; msg?: string; data?: T }
type DailyForecastDto = { Date?: string; date?: string; TempMaxC?: number; tempMaxC?: number; TempMinC?: number; tempMinC?: number; WeatherCode?: number; weatherCode?: number; WeatherText?: string; weatherText?: string }
type WindDirectionStat = { Name?: string; name?: string; Count?: number; count?: number; Percent?: number; percent?: number }
type DayWarning = { date: string; level: number; items: string[] }
type WeeklyWarningDto = { city: string; days: number; maxLevel: number; summary: string; details: DayWarning[] }

const form = reactive({
  city: '北京',
  days: 7 as 3 | 5 | 7 | 10 | 14,
  cities: [] as string[],
})

const defaultCities = [
  '北京','天津','上海','重庆','石家庄','太原','呼和浩特','沈阳','长春','哈尔滨','南京','杭州','合肥','福州','南昌','济南','郑州','武汉','长沙','广州','南宁','海口','成都','贵阳','昆明','西安','兰州','西宁','拉萨','银川','乌鲁木齐','台北','香港','澳门'
]

const forecastRef = ref<HTMLDivElement | null>(null)
const windRef = ref<HTMLDivElement | null>(null)
const mapRef = ref<HTMLDivElement | null>(null)
let forecastChart: echarts.ECharts | null = null
let windChart: echarts.ECharts | null = null
let bmap: any = null
let usingGL = true

const loading = reactive({ forecast: false, wind: false, warning: false })
let loadingAll = false
const forecastText = ref('')
const route = useRoute()
const warningData = ref<WeeklyWarningDto | null>(null)
const isCompact = ref(false)

// 简单防抖，避免输入城市名时每个键触发地理编码
function debounce<T extends (...args: any[]) => void>(fn: T, wait = 500) {
  let t: number | undefined
  return (...args: Parameters<T>) => {
    if (t) window.clearTimeout(t)
    t = window.setTimeout(() => fn(...args), wait)
  }
}

// 将文字预报解析为结构化数据，便于美化展示
const currentTemp = computed(() => {
  const m = forecastText.value.match(/当前温度[:：]\s*([\d.]+)℃/)
  return m ? Number(m[1]) : null
})
type ForecastItem = { date: string; high: number; low: number }
const forecastItems = computed<ForecastItem[]>(() => {
  if (!forecastText.value) return []
  const lines = forecastText.value.split(/\r?\n/).map(s => s.trim()).filter(Boolean)
  const items: ForecastItem[] = []
  for (const line of lines) {
    const m = line.match(/(\d{4}-\d{2}-\d{2}).*?最[高高]\s*([\d.]+)℃.*?最[低低]\s*([\d.]+)℃/)
    if (m) items.push({ date: m[1], high: Number(m[2]), low: Number(m[3]) })
  }
  return items
})

function resize() {
  if (forecastChart && forecastRef?.value) {
    forecastChart.resize()
  }
  if (windChart && windRef?.value) {
    windChart.resize()
  }
  // 地图容器自适应
  if (bmap) {
    try { bmap.checkResize && bmap.checkResize() } catch {}
  }
  // 自动紧凑：在较小窗口下启用紧凑模式
  const w = window.innerWidth
  const h = window.innerHeight
  isCompact.value = w < 1600 || h < 900
}

onMounted(() => {
  if (forecastRef.value) forecastChart = echarts.init(forecastRef.value)
  if (windRef.value) windChart = echarts.init(windRef.value)
  // 地图初始化
  setupMap()
  resize()
  window.addEventListener('resize', resize)
  loadAll()
})

// 若页面通过 keep-alive 缓存，每次返回页面时自动刷新
onActivated(() => {
  loadAll()
})

// 当路由参数或查询变化（包括从其它页面跳回该路由）时自动刷新
watch(() => route.fullPath, () => {
  loadAll()
})
onBeforeRouteUpdate((_to, _from, next) => {
  loadAll()
  next()
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', resize)
  forecastChart?.dispose(); forecastChart = null
  windChart?.dispose(); windChart = null
})

async function loadAll() {
  if (loadingAll) return
  loadingAll = true
  try {
    await Promise.all([loadForecast(), loadWind(), loadForecastText(), loadWeeklyWarning()])
  } finally {
    loadingAll = false
  }
}

async function loadForecast() {
  if (!form.city) return
  loading.forecast = true
  try {
    // 将接口路径集中管理，方便以后整体替换
    const FORECAST_URL = '/api/Weather/forecast'
    const { data } = await MES_workbenchApi.get<ApiResult<DailyForecastDto[]>>(FORECAST_URL, {
      params: { city: form.city, days: form.days }
    })
    const list = (data?.data || []) as DailyForecastDto[]
    renderForecast(normalizeForecast(list))
  } catch (e: any) {
    ElMessage.error(e?.response?.data?.msg || '获取天气预报失败')
  } finally {
    loading.forecast = false
  }
}

async function loadForecastText() {
  if (!form.city) { forecastText.value = ''; return }
  try {
    const FORECAST_TEXT_URL = '/api/Weather/get-forecast-by-city'
    const { data } = await MES_workbenchApi.get<ApiResult<string>>(FORECAST_TEXT_URL, {
      params: { city: form.city, days: form.days }
    })
    forecastText.value = data?.data || ''
  } catch {
    forecastText.value = ''
  }
}

async function loadWind() {
  loading.wind = true
  try {
    const citiesParam = form.cities.length ? form.cities.join(',') : undefined
    const WIND_URL = '/api/Weather/wind-direction-stats'
    const { data } = await MES_workbenchApi.get<ApiResult<WindDirectionStat[]>>(WIND_URL, {
      params: { cities: citiesParam }
    })
    const list = (data?.data || []) as WindDirectionStat[]
    renderWind(normalizeWind(list))
  } catch (e: any) {
    ElMessage.error(e?.response?.data?.msg || '获取风向统计失败')
  } finally {
    loading.wind = false
  }
}

function normalizeForecast(list: DailyForecastDto[]) {
  return list.map(x => ({
    Date: x.Date ?? x.date ?? '',
    TempMaxC: (x.TempMaxC ?? x.tempMaxC) ?? NaN,
    TempMinC: (x.TempMinC ?? x.tempMinC) ?? NaN,
    WeatherCode: (x.WeatherCode ?? x.weatherCode) ?? 0,
    WeatherText: x.WeatherText ?? x.weatherText ?? ''
  }))
}

function renderForecast(list: ReturnType<typeof normalizeForecast>) {
  if (!forecastChart) return
  const dates = list.map(x => x.Date)
  const tmax = list.map(x => Number.isFinite(x.TempMaxC) ? x.TempMaxC as number : null as any)
  const tmin = list.map(x => Number.isFinite(x.TempMinC) ? x.TempMinC as number : null as any)
  const hasSeries = tmax.some(v => v !== null) || tmin.some(v => v !== null)
  const xData = dates.some(Boolean) ? dates : list.map((_, i) => String(i + 1))
  const hasData = list.length > 0 && hasSeries
  const option: echarts.EChartsOption = hasData ? {
    color: ['#5470C6', '#91CC75'],
    tooltip: { trigger: 'axis' },
    legend: { data: ['最高温', '最低温'], top: 8 },
    grid: { left: 20, right: 20, bottom: 56, top: 56, containLabel: true },
    xAxis: { type: 'category', data: xData, axisTick: { show: false }, axisLabel: { margin: 12 } },
    yAxis: { type: 'value', name: '℃', splitLine: { lineStyle: { type: 'dashed' } }, axisLabel: { margin: 10 } },
    dataZoom: [
      { type: 'inside', zoomOnMouseWheel: false, moveOnMouseMove: true },
      { type: 'slider', height: 16, bottom: 8, handleSize: 12, brushSelect: false }
    ],
    series: [
      { name: '最高温', type: 'line', data: tmax, smooth: true, symbol: 'circle', symbolSize: 5,
        lineStyle: { width: 2 }, areaStyle: { opacity: 0.12 } },
      { name: '最低温', type: 'line', data: tmin, smooth: true, symbol: 'circle', symbolSize: 5,
        lineStyle: { width: 2 }, areaStyle: { opacity: 0.12 } },
    ]
  } : {
    title: { text: '暂无数据', left: 'center', top: 'middle', textStyle: { color: '#909399' } },
    xAxis: { type: 'category', data: [] },
    yAxis: { type: 'value' },
    series: []
  }
  forecastChart.setOption(option, true)
}

async function loadWeeklyWarning() {
  if (!form.city) { warningData.value = null; return }
  loading.warning = true
  try {
    const URL = '/api/Weather/weekly-warning'
    const { data } = await MES_workbenchApi.get<ApiResult<WeeklyWarningDto>>(URL, { params: { city: form.city } })
    warningData.value = (data?.data || null) as WeeklyWarningDto | null
  } catch (e: any) {
    warningData.value = null
  } finally {
    loading.warning = false
  }
}

function normalizeWind(list: WindDirectionStat[]) {
  return list.map(x => ({
    Name: x.Name ?? x.name ?? '',
    Count: (x.Count ?? x.count) ?? 0,
    Percent: (x.Percent ?? x.percent) ?? 0,
  }))
}

function renderWind(list: ReturnType<typeof normalizeWind>) {
  if (!windChart) return
  const hasData = Array.isArray(list) && list.some(x => (x?.Percent ?? 0) > 0)
  const data = list.map(x => ({ name: String(x.Name || ''), value: Number(x.Percent || 0) }))
  const option: echarts.EChartsOption = hasData ? {
    tooltip: { trigger: 'item', formatter: '{b}: {d}% ({c})' },
    legend: { show: false },
    series: [
      {
        name: '风向',
        type: 'pie',
        center: ['50%', '50%'],
        radius: ['58%', '76%'],
        avoidLabelOverlap: true,
        itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
        label: {
          show: true,
          color: '#FFD166',
          formatter: '{d}%\n{b}',
          fontSize: 12,
          lineHeight: 16
        },
        labelLine: {
          show: true,
          length: 14,
          length2: 10,
          smooth: true,
          lineStyle: { color: '#FFD166' }
        },
        emphasis: { label: { show: true, fontSize: 14, fontWeight: 'bold' } },
        data,
      }
    ]
  } : {
    title: { text: '暂无数据', left: 'center', top: 'middle', textStyle: { color: '#909399' } },
    series: []
  }
  windChart.setOption(option, true)
}

// ---------- Baidu Map (WebGL) ----------
const BAIDU_AK = 'mE6QSojiMtfqTZxqoqGKakiktU0AWEw9'

function loadBMap(): Promise<void> {
  return new Promise((resolve, reject) => {
    const w = window as any
    if (w.BMapGL) { resolve(); return }
    if (w.__bmapLoading) { w.__bmapQueue.push({ resolve, reject }); return }
    w.__bmapLoading = true
    w.__bmapQueue = [{ resolve, reject }]
    const script = document.createElement('script')
    // 显式加载 Geocoder 插件，保证地理编码可用
    script.src = `https://api.map.baidu.com/api?v=1.0&type=webgl&ak=${BAIDU_AK}&plugin=Geocoder`
    script.async = true
    script.onload = () => {
      const queue = w.__bmapQueue || []
      if (w.BMapGL) {
        queue.forEach((q: any) => q.resolve())
      } else {
        queue.forEach((q: any) => q.reject(new Error('BMapGL 未定义，可能为 AK/Referer 限制或脚本被拦截')))
      }
      w.__bmapQueue = []
    }
    script.onerror = (e) => {
      const queue = w.__bmapQueue || []
      queue.forEach((q: any) => q.reject(e))
      w.__bmapQueue = []
    }
    document.head.appendChild(script)
  })
}

function loadBMapLegacy(): Promise<void> {
  return new Promise((resolve, reject) => {
    const w = window as any
    if (w.BMap) { resolve(); return }
    if (w.__bmapLegacyLoading) { w.__bmapLegacyQueue.push({ resolve, reject }); return }
    w.__bmapLegacyLoading = true
    w.__bmapLegacyQueue = [{ resolve, reject }]
    const script = document.createElement('script')
    // JSAPI 3.0（非 WebGL）
    script.src = `https://api.map.baidu.com/api?v=3.0&ak=${BAIDU_AK}`
    script.async = true
    script.onload = () => {
      const queue = w.__bmapLegacyQueue || []
      if (w.BMap) {
        queue.forEach((q: any) => q.resolve())
      } else {
        queue.forEach((q: any) => q.reject(new Error('BMap 未定义，脚本可能被拦截或 AK/Referer 受限')))
      }
      w.__bmapLegacyQueue = []
    }
    script.onerror = (e) => {
      const queue = w.__bmapLegacyQueue || []
      queue.forEach((q: any) => q.reject(e))
      w.__bmapLegacyQueue = []
    }
    document.head.appendChild(script)
  })
}

async function setupMap() {
  if (!mapRef.value) return
  try {
    await loadBMap()
    await nextTick()
    // 再次确认容器已挂载并有尺寸
    if (!mapRef.value) return
    // 等待 BMapGL 完整可用
    await waitFor(() => !!((window as any).BMapGL && (window as any).BMapGL.Map), 30, 50)
    const BMapGL = (window as any).BMapGL
    if (!BMapGL || !BMapGL.Map) throw new Error('BMapGL 未就绪')
    bmap = new BMapGL.Map(mapRef.value)
    bmap.enableScrollWheelZoom(true)
    // 默认全国视角
    bmap.centerAndZoom(new BMapGL.Point(116.404, 39.915), 5)
    // 按当前城市定位
    centerToCity(form.city)
  } catch (e: any) {
    console.warn('BMapGL 初始化失败，尝试降级到 BMap：', e)
    try {
      usingGL = false
      await loadBMapLegacy()
      await nextTick()
      await waitFor(() => !!((window as any).BMap && (window as any).BMap.Map), 30, 50)
      if (!mapRef.value) return
      const BMap = (window as any).BMap
      if (!BMap || !BMap.Map) throw new Error('BMap 未就绪')
      bmap = new BMap.Map(mapRef.value)
      bmap.enableScrollWheelZoom(true)
      bmap.centerAndZoom(new BMap.Point(116.404, 39.915), 5)
      centerToCity(form.city)
    } catch (ee: any) {
      console.warn('BMap 降级也失败：', ee)
      ElMessage.warning(ee?.message || '地图加载失败，请检查网络或 AK 白名单设置')
    }
  }
}

function centerToCity(city: string) {
  if (!city) return
  const w: any = window as any
  if (!bmap) return
  try {
    if (usingGL && w.BMapGL) {
      const geocoder = new w.BMapGL.Geocoder()
      geocoder.getPoint(city, (pt: any) => {
        if (pt) {
          bmap.centerAndZoom(pt, 10)
          bmap.clearOverlays()
          const marker = new w.BMapGL.Marker(pt)
          bmap.addOverlay(marker)
          const label = new w.BMapGL.Label(city, { position: pt, offset: new w.BMapGL.Size(12, -18) })
          label.setStyle({ color: '#333', borderColor: '#999', borderRadius: '4px', fontSize: '12px', padding: '2px 6px' })
          bmap.addOverlay(label)
        } else { bmap.setZoom(8) }
      }, city)
    } else if (w.BMap) {
      const geocoder = new w.BMap.Geocoder()
      geocoder.getPoint(city, (pt: any) => {
        if (pt) {
          bmap.centerAndZoom(pt, 10)
          bmap.clearOverlays()
          const marker = new w.BMap.Marker(pt)
          bmap.addOverlay(marker)
          const label = new w.BMap.Label(city, { position: pt, offset: new w.BMap.Size(12, -18) })
          label.setStyle({ color: '#333', borderColor: '#999', borderRadius: '4px', fontSize: '12px', padding: '2px 6px' })
          bmap.addOverlay(label)
        } else { bmap.setZoom(8) }
      }, city)
    }
  } catch {}
}

// 等待条件满足：最多 tries 次，每次间隔 intervalMs 毫秒
function waitFor(cond: () => boolean, tries = 20, intervalMs = 50): Promise<void> {
  return new Promise((resolve, reject) => {
    let left = tries
    const tick = () => {
      try {
        if (cond()) { resolve(); return }
      } catch {}
      if (--left <= 0) { reject(new Error('waitFor timeout')); return }
      setTimeout(tick, intervalMs)
    }
    tick()
  })
}

// 若城市或天数改变，自动刷新预报
watch(() => form.city, () => { loadForecast(); loadForecastText() })
watch(() => form.days, () => { loadForecast(); loadForecastText() })
watch(() => form.city, () => { loadWeeklyWarning() })
const debouncedCenterToCity = debounce((c: string) => { centerToCity(c) }, 500)
watch(() => form.city, (c) => {
  if (!c || c.trim().length < 2) return
  debouncedCenterToCity(c.trim())
})

</script>

<style scoped>
.warning-page {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.toolbar {
  padding-bottom: 4px;
}
.content {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 12px;
}
.panel {
  grid-column: span 12;
}
.panel--half { grid-column: span 12; }
@media (min-width: 960px) { .panel--half { grid-column: span 6; } }
.panel-full { grid-column: span 12 !important; }
.forecast-card { max-width: none; margin: 0; }
.chart {
  width: 100%;
  height: 300px;
}
.map {
  width: 100%;
  height: 360px;
  border-radius: 6px;
  overflow: hidden;
}
.forecast-text {
  margin-top: 12px;
  color: #666;
  white-space: pre-wrap;
}

/* 新的文字预报样式 */
.forecast-box {
  margin-top: 8px;
  padding: 14px 16px;
  border-radius: 8px;
  background: linear-gradient(180deg, #f7faff 0%, #ffffff 100%);
}
.forecast-box--compact { /* 取消缩放，保证可读性 */ }
.forecast-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}
.forecast-list {
  display: grid;
  /* 默认每行显示四条 */
  grid-template-columns: repeat(4, minmax(220px, 1fr));
  gap: 14px 18px;
  list-style: none;
  padding: 0;
  margin: 0;
}
.panel--half .forecast-list { grid-template-columns: repeat(2, minmax(300px, 1fr)); }
@media (max-width: 1600px) { .forecast-list { grid-template-columns: repeat(3, minmax(220px, 1fr)); } }
@media (max-width: 1200px) { .forecast-list { grid-template-columns: repeat(2, minmax(220px, 1fr)); } }
@media (max-width: 720px) {
  .forecast-list { grid-template-columns: 1fr; }
}
.forecast-item {
  display: grid;
  /* 固定日期列宽；两个标签按内容宽度排布，避免被拉伸 */
  grid-template-columns: 120px max-content max-content;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border: 1px solid #eef2f6;
  border-radius: 10px;
  background: #fff;
  transition: box-shadow .2s ease, transform .2s ease;
  /* 缩小每个卡片的背景宽度 */
  width: 92%;
  margin: 0 auto;
}
.forecast-item:hover {
  box-shadow: 0 2px 10px rgba(0,0,0,.05);
}
.forecast-item .date {
  font-weight: 600;
  color: #606266;
  min-width: 96px;
}
.tag {
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid transparent;
  white-space: nowrap;
}
.tag--high {
  color: #b14c1b;
  background: #fff2e8;
  border-color: #ffe1cc;
}
.tag--low {
  color: #1a7f52;
  background: #e7fff3;
  border-color: #c6f5e0;
}
.forecast-raw {
  margin: 0;
  color: #666;
}

/* Weekly warning styles */
.warning-card { display: flex; flex-direction: column; gap: 10px; }
.warning-summary { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.summary-text { color: #606266; }
.level-chip { padding: 2px 8px; border-radius: 999px; font-size: 12px; border: 1px solid #eee; }
.level-0 { background: #f5f7fa; color: #606266; }
.level-1 { background: #e6f7ff; color: #10659e; border-color: #b8e4ff; }
.level-2 { background: #fff7e6; color: #b15c00; border-color: #ffe0b2; }
.level-3 { background: #ffe7e6; color: #b10000; border-color: #ffc3c0; }
.warning-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(220px, 1fr)); gap: 8px 12px; }
@media (min-width: 1200px) { .warning-list { grid-template-columns: repeat(4, minmax(200px, 1fr)); } }
@media (max-width: 960px) { .warning-list { grid-template-columns: repeat(2, minmax(200px, 1fr)); } }
@media (max-width: 720px) { .warning-list { grid-template-columns: 1fr; } }
.warning-item { background: #fff; border: 1px solid #eef2f6; border-radius: 6px; padding: 6px 8px; display: flex; flex-direction: column; gap: 6px; }
.warning-date { font-weight: 600; color: #606266; }
.warning-tags { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.level-badge { font-size: 12px; padding: 2px 6px; border-radius: 4px; border: 1px solid transparent; }
.warn-tag { font-size: 12px; padding: 2px 8px; border-radius: 999px; background: #f5f7fa; color: #606266; border: 1px solid #e4e7ed; }
.warning-empty { color: #909399; text-align: center; padding: 12px 0; }

/* 紧凑模式总体缩放与密度降低 */
.compact {
  transform: scale(0.95);
  transform-origin: top center;
}
.compact .content { gap: 10px; }
.compact .chart { height: 260px; }
.compact .map { height: 260px; }
.compact :deep(.el-card__body) { padding: 12px !important; }
.compact .warning-list { gap: 6px 10px; }
.compact .forecast-text { max-height: 64px; overflow: hidden; }
</style>